<?php

namespace app\api\service;

use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use think\facade\Request;

/**
 * 单例 一次请求中所有出现jwt的地方都是一个用户
 * Class JwtAuth
 * @package app\api\service
 */
class JwtAuth
{
    protected $key;
    protected $jwtAlgo;
    protected $jwtIss;
    protected $jwtAud;
    protected $tokenName;

    protected $uid;
    protected $decodedCache = null;

    private static $instance = null;

    public function __construct()
    {
        $this->key = config('jwt.jwt_key');
        $this->jwtAlgo = config('jwt.jwt_algo');
        $this->jwtIss = config('jwt.jwt_iss');
        $this->jwtAud = config('jwt.jwt_aud');
        $this->tokenName = config('jwt.token_name');

    }

    // 禁止克隆
    private function __clone() {}

    // 禁止反序列化
    private function __wakeup() {}

    // 获取单例实例
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 设置用户ID
    public function setUid($uid)
    {
        $this->uid = $uid;
        return $this;
    }

    // 触发编码
    public function encode()
    {
        return $this;
    }

    public function getToken($info, $idName = 'userId')
    {
        $jwtExpHour = config('jwt.jwt_exp_hour');
        $time = time();
        $payload = [
            'iss' => $this->jwtIss,
            'aud' => $this->jwtAud,
            'iat' => $time,
            'nbf' => $time,
            'exp' => $time + 3600 * $jwtExpHour,
            'username' => $info['user_name'],
            (string)$idName => $info['user_id']
        ];
        return JWT::encode($payload, $this->key, $this->jwtAlgo);
    }

    // 校验token的方法(在需要校验token的接口中调用)
    public function checkToken($flag = false, $_token = null)
    {
        // 1. 获取并校验 Token 格式
        $header = !empty($_token) ? $_token : Request::instance()->header($this->tokenName);

        if (empty($header)) {
            return ['status' => false, 'msg' => 'token为空'];
        }
        $token = trim(str_replace('Bearer', '', $header));
        if ($token === '') {
            return ['status' => false, 'msg' => 'token格式错误'];
        }

        // 校验 token 格式
        if (count(explode('.', $token)) !== 3) {
            return ['status' => false, 'msg' => 'token格式错误'];
        }
        try {
            // 2. 移除冗余解密
            $decoded = JWT::decode($token, new Key($this->key, $this->jwtAlgo));

            // 3. 缓存解码结果
            $this->decodedCache = $decoded;

            // 校验 Issuer/Audience
            if ($decoded->iss !== $this->jwtIss || $decoded->aud !== $this->jwtAud) {
                return ['status' => false, 'msg' => 'token不合法'];
            }

            // 校验过期时间
            if ($decoded->exp < time()) {
                return ['status' => false, 'msg' => 'token已过期'];
            }

            // 动态校验 ID 字段（假设配置中定义了 idName）
            $idName = config('jwt.id_name', 'userId'); // 从配置获取键名
            if (empty($decoded->$idName)) {
                return ['status' => false, 'msg' => 'token参数为空'];
            }

            if ($flag) {
                return $decoded;
            }
            return ['status' => true, 'msg' => 'token正确'];
        } catch (SignatureInvalidException $e) {
            return ['status' => false, 'msg' => '签名无效'];
        } catch (ExpiredException $e) {
            return ['status' => false, 'msg' => 'token已过期'];
        } catch (\Exception $e) {
            return ['status' => false, 'msg' => '未知错误: ' . $e->getMessage()];
        }
    }

    // 获取用户id
    public function getUserId()
    {

        if ($this->decodedCache === null) {
            $decoded = $this->checkToken(true);
        } else {
            $decoded = $this->decodedCache;
        }

        // 防御性访问
        if (is_object($decoded) && isset($decoded->{config('jwt.id_name', 'userId')})) {
            return $decoded->{config('jwt.id_name', 'userId')};
        }
        return null;
    }

    public function getUserName()
    {
        if ($this->decodedCache === null) {
            $decoded = $this->checkToken(true);
        } else {
            $decoded = $this->decodedCache;
        }

        // 防御性访问
        if (is_object($decoded) && isset($decoded->username)) {
            return $decoded->username;
        }
        return null;
    }

}
